#include <fermat/net/socket_option.h>
#include <fermat/common/logging.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
//#include <linux/tcp.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/tcp.h>
#include <unistd.h>
#include <net/if_arp.h>

namespace fermat {
namespace net {
    
    //! Turn socket into non-blocking state.
bool SocketOption::setNonBlock(int fd, bool on)
{
    bool rc=false;

    int flags = fcntl(fd, F_GETFL, NULL);
    if (flags >= 0) {
        if (!on) {
            flags &= ~O_NONBLOCK; // clear nonblocking
        } else {
            flags |= O_NONBLOCK;  // set nonblocking
        }

        if (::fcntl(fd, F_SETFL, flags) >= 0) {
                rc = true;
        }
    }

    return rc;
}

    //! Return the current local socket address.
SocketAddress SocketOption::getLocalAddress(int fd)
{
    assert(fd > 0);
    struct sockaddr_in6 sa;
    socklen_t salen = sizeof(sa);
    SocketAddress ret;
    if (getsockname(fd, reinterpret_cast<struct sockaddr*>(&sa), &salen) != 0) {

            return ret;
    }
    ret.set(reinterpret_cast<struct sockaddr*>(&sa), salen);
    return ret;
}

     //! Return the current peer socket address.
SocketAddress SocketOption::getPeerAddress(int fd)
{
    assert(fd > 0);

    struct sockaddr_in6 sa;
    socklen_t salen = sizeof(sa);
    SocketAddress ret;
    if (::getpeername(fd, reinterpret_cast<struct sockaddr*>(&sa), &salen) != 0) {
        LOG_ERROR("Socket::GetPeerAddress() fd = {} error = {} ", fd, strerror(errno));
        return ret;
    }
    ret.set(reinterpret_cast<struct sockaddr*>(&sa), salen);
    return ret;
}

bool SocketOption::shutdownRead(int fd)
{
    return ::shutdown(fd, SHUT_RD) == 0;
}
bool SocketOption::shutdownWrite(int fd)
{
    return ::shutdown(fd, SHUT_WR) == 0;
}
bool SocketOption::shutdownAll(int fd)
{
    return ::shutdown(fd, SHUT_RDWR) == 0;
}

bool SocketOption::close(int fd)
{
    return ::close(fd) == 0;
}

bool SocketOption::getsockopt(int fd, int level, int optname,
                   void* optval, socklen_t* optlen)
{
    assert(fd > 0);
    int r = ::getsockopt(fd, level, optname, optval, optlen);
    return r == 0;
}

bool SocketOption::setsockopt(int fd, int level, int optname,
                   const void* optval, socklen_t optlen)
{
    assert(fd > 0);
    int r = ::setsockopt(fd, level, optname, optval, optlen);
    return r == 0;
}

    //! Enable sending of keep-alive messages on connection-oriented sockets.
bool SocketOption::setKeepAlive(int fd, bool activate)
{
     int sockoptflag = (activate ? 1 : 0);
    if (::setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE,
                     &sockoptflag, sizeof(sockoptflag)) != 0) {
        LOG_ERROR("Cannot set SO_KEEPALIVE on socket fd {} : {} ", fd, strerror(errno));
        return false;
    }
    return true;
}
     //! Enable SO_REUSEADDR, which allows the socket to be bound more quickly to
    //! previously used ports.
bool SocketOption::setReuseAddr(int fd, bool activate)
{
    int sockoptflag = (activate ? 1 : 0);
    if (::setsockopt(fd, SOL_SOCKET, SO_REUSEADDR,
                     &sockoptflag, sizeof(sockoptflag)) != 0) {
        LOG_ERROR("Cannot set SO_REUSEADDR on socket fd {} : {}", fd, strerror(errno));
        return false;
    }   
    return true;
}
bool SocketOption::setReusePort(int fd, bool activate)
{
    int sockoptflag = (activate ? 1 : 0);
    if (::setsockopt(fd, SOL_SOCKET, SO_REUSEPORT,
                     &sockoptflag, sizeof(sockoptflag)) != 0) {
        LOG_ERROR("Cannot set SO_REUSEPORT on socket fd {} : {}", fd, strerror(errno));
            return false;
    }
    return true;
}
bool SocketOption::setSoLinger(int fd, bool doLinger, int seconds)
{
    bool rc=false;
    struct linger lingerTime;
    lingerTime.l_onoff = doLinger ? 1 : 0;
    lingerTime.l_linger = seconds;
    rc = (setsockopt(fd, SOL_SOCKET, SO_LINGER,(const void *)(&lingerTime), sizeof(lingerTime)) == 0);
    return rc;
}

bool SocketOption::setTcpQuickAck(int fd, bool quickAck)
{
    bool rc = false;
    int quickAckInt = quickAck ? 1 : 0;
    rc = (setsockopt(fd, IPPROTO_TCP, TCP_QUICKACK,
          (const void *)(&quickAckInt), sizeof(quickAckInt)) == 0);
    return rc;        
}

bool SocketOption::setTimeOption(int fd, int option, int milliseconds)
{
    bool rc=false;
    struct timeval timeout;
    timeout.tv_sec = (int)(milliseconds / 1000);
    timeout.tv_usec = (milliseconds % 1000) * 1000000;
    rc = (::setsockopt(fd, SOL_SOCKET, option,
                         (const void *)(&timeout), sizeof(timeout)) == 0);
    return rc;
}

    //! If set, disable the Nagle algorithm. This means that segments are always
    //! sent as soon as possible, even if there is only a small amount of data.
bool SocketOption::setNoDelay(int fd, bool activate)
{
    assert(fd > 0);
    int sockoptflag = (activate ? 1 : 0);
    if (::setsockopt(fd, IPPROTO_TCP, TCP_NODELAY,
                     &sockoptflag, sizeof(sockoptflag)) != 0) {
        LOG_ERROR("Cannot set TCP_NODELAY on socket fd {} : {}", fd, strerror(errno));
        return false;
    }
    return true;
}

    //! Set SO_SNDBUF socket option.
bool SocketOption::setSndBuf(int fd, size_t size)
{
    assert(fd > 0);
    int sockoptflag = static_cast<int>(size);
    if (::setsockopt(fd, SOL_SOCKET, SO_SNDBUF,
                     &sockoptflag, sizeof(sockoptflag)) != 0) {
            LOG_ERROR("Cannot set SO_SNDBUF on socket fd {} : {}", fd, strerror(errno));
        return false;
    }
    return true;
}

    //! Set SO_RCVBUF socket option.
bool SocketOption::setRcvBuf(int fd, size_t size)
{
    int sockoptflag = static_cast<int>(size);
    if (::setsockopt(fd, SOL_SOCKET, SO_RCVBUF,
                     &sockoptflag, sizeof(sockoptflag)) != 0) {
        LOG_ERROR("Cannot set SO_RCVBUF on socket fd {} : {}", fd, strerror(errno));
        return false;
    }
    return true;
}


bool SocketOption::getTcpInfo(int fd, struct tcp_info *tcpi) 
{
    socklen_t len = sizeof(struct tcp_info);
    bzero(tcpi, len);
    return ::getsockopt(fd, SOL_TCP, TCP_INFO, tcpi, &len) == 0;
}

bool SocketOption::getTcpInfoString(int fd, char* buf, int len)
{
    struct tcp_info tcpi;
    bool ok = getTcpInfo(fd, &tcpi);
    if (ok) {
    	snprintf(buf, len, "unrecovered=%u "
             "rto=%u ato=%u snd_mss=%u rcv_mss=%u "
             "lost=%u retrans=%u rtt=%u rttvar=%u "
             "sshthresh=%u cwnd=%u total_retrans=%u",
             tcpi.tcpi_retransmits,  // Number of unrecovered [RTO] timeouts
             tcpi.tcpi_rto,          // Retransmit timeout in usec
             tcpi.tcpi_ato,          // Predicted tick of soft clock in usec
             tcpi.tcpi_snd_mss,
             tcpi.tcpi_rcv_mss,
             tcpi.tcpi_lost,         // Lost packets
             tcpi.tcpi_retrans,      // Retransmitted packets out
             tcpi.tcpi_rtt,          // Smoothed round trip time in usec
             tcpi.tcpi_rttvar,       // Medium deviation
             tcpi.tcpi_snd_ssthresh,
             tcpi.tcpi_snd_cwnd,
             tcpi.tcpi_total_retrans);  
    }
    return ok;
}

bool SocketOption::setSendTimeOut(int fd, Timespan &span)
{
	struct timeval tv;
	tv.tv_sec  = (long) span.totalSeconds();
	tv.tv_usec = (long) span.useconds();
    return ::setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, (const void *)(&tv), sizeof(tv)) == 0;
}

bool SocketOption::setRecvTimeOut(int fd, Timespan &span)
{
	struct timeval tv;
	tv.tv_sec  = (long) span.totalSeconds();
	tv.tv_usec = (long) span.useconds();
    return ::setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, (const void *)(&tv), sizeof(tv)) == 0;
}

}
}